/******************************************************************************************************
Copyright (C) 2022 Mestrelab Research S.L. All rights reserved.

This file is part of the Mnova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*******************************************************************************************************/

/*globals Application, Dialog, File, FileDialog, GroupBox, LineEdit, MessageBox, MnUi, molecule, PushButton, settings, TextStream */

/* jslint plusplus: true, indent: 4, continue:true, regexp: true */

function RenumberMolecule() {
	"use strict";
}

RenumberMolecule.keys = {
	mapFile: "Renumber Molecule/Map File",
	inputFile: "Renumber Molecule/Input File",
	outputFile: "Renumber Molecule/Output File",
	separator: "Renumber Molecule/separator",
	header: "Renumber Molecule/header",
	activeMol: "Renumber Molecule/activeMol",
	addMol: "Renumber Molecule/addMol"
};

RenumberMolecule.SPACE = 0;
RenumberMolecule.COMMA = 1;
RenumberMolecule.SEMICOLON = 2;

RenumberMolecule.getStartDir = function (p) {
	"use strict";

	var f, startDir;

	try {
		f = new File(p);
		startDir = f.absDirPath;
	} catch (e) {}
	return startDir;
};

RenumberMolecule.makeRenumberDialog = function () {
	"use strict";

	var dialog, ok, result;

	dialog = Application.loadUiFile("ricares:RenumberMoleculeDialog.ui");

	dialog.widgets.header.checked = settings.value(RenumberMolecule.keys.header, true);
	dialog.widgets.separator.currentIndex = settings.value(RenumberMolecule.keys.separator, 0);
	dialog.widgets.activeMol.checked = settings.value(RenumberMolecule.keys.activeMol, false);
	dialog.widgets.addMol.checked = settings.value(RenumberMolecule.keys.addMol, false);

	dialog.widgets.lineEditOutput.enabled = !dialog.widgets.addMol.checked;
	dialog.widgets.toolButtonOutput.enabled = !dialog.widgets.addMol.checked;

	dialog.widgets.lineEditInput.enabled = !dialog.widgets.activeMol.checked;
	dialog.widgets.toolButtonInput.enabled = !dialog.widgets.activeMol.checked;

	dialog.widgets.lineEditMapFile.text = settings.value(RenumberMolecule.keys.mapFile, "");
	dialog.widgets.toolButtonMapFile.clicked.connect(function () {
		var filename;
		filename = FileDialog.getOpenFileName(
			"*.txt",
			"select map file",
			RenumberMolecule.getStartDir(dialog.widgets.lineEditMapFile.text)
		);
		if (filename) {
			dialog.widgets.lineEditMapFile.text = filename;
		}
	});

	dialog.widgets.lineEditInput.text = settings.value(RenumberMolecule.keys.inputFile, "");
	dialog.widgets.toolButtonInput.clicked.connect(function () {
		var filename;

		filename = FileDialog.getOpenFileName(
			"*.mol",
			"select mol file",
			RenumberMolecule.getStartDir(dialog.widgets.lineEditInput.text)
		);
		if (filename) {
			dialog.widgets.lineEditInput.text = filename;
		}
	});

	dialog.widgets.lineEditOutput.text = settings.value(RenumberMolecule.keys.outputFile, "");
	dialog.widgets.toolButtonOutput.clicked.connect(function () {
		var filename;

		filename = FileDialog.getSaveFileName(
			"*.mol",
			"select output mol file",
			RenumberMolecule.getStartDir(dialog.widgets.lineEditOutput.text)
		);
		if (filename) {
			dialog.widgets.lineEditOutput.text = filename;
		}
	});

	dialog.widgets.addMol.clicked.connect(function () {
		dialog.widgets.lineEditOutput.enabled = !dialog.widgets.addMol.checked;
		dialog.widgets.toolButtonOutput.enabled = !dialog.widgets.addMol.checked;
	});

	dialog.widgets.activeMol.clicked.connect(function () {
		dialog.widgets.lineEditInput.enabled = !dialog.widgets.activeMol.checked;
		dialog.widgets.toolButtonInput.enabled = !dialog.widgets.activeMol.checked;
	});

	dialog.widgets.lineEditMapFile.setFocus();

	ok = dialog.exec();

	result = {
		ok: ok,
		mapfile: dialog.widgets.lineEditMapFile.text,
		infile: dialog.widgets.lineEditInput.text,
		outfile: dialog.widgets.lineEditOutput.text,
		header: dialog.widgets.header.checked,
		separator: dialog.widgets.separator.currentIndex,
		addMol: dialog.widgets.addMol.checked,
		activeMol: dialog.widgets.activeMol.checked
	};

	if (result.ok) {
		settings.setValue(RenumberMolecule.keys.mapFile, result.mapfile);
		settings.setValue(RenumberMolecule.keys.inputFile, result.infile);
		settings.setValue(RenumberMolecule.keys.outputFile, result.outfile);
		settings.setValue(RenumberMolecule.keys.separator, result.separator);
		settings.setValue(RenumberMolecule.keys.header, result.header);
		settings.setValue(RenumberMolecule.keys.activeMol, result.activeMol);
		settings.setValue(RenumberMolecule.keys.addMol, result.addMol);
	}

	return result;
};

RenumberMolecule.test = function () {
	"use strict";

	var rnm = new RenumberMolecule();
	rnm.run();
};

RenumberMolecule.padString = function (s, n) {
	"use strict";

	var i, pad, sn;

	if (s.length >= n) {
		return sn;
	}
	pad = "";
	for (i = 0; i < n - s.length; i++) {
		pad = pad + " ";
	}
	return pad + s;
};

RenumberMolecule.renumberMol = function (config) {
	"use strict";

	var i,
		j,
		regexp,
		a1,
		a2,
		f,
		fm,
		fo,
		ind,
		inds,
		la,
		laOut,
		line,
		lines,
		lt,
		mapLines,
		mla,
		na1,
		na2,
		natoms,
		nbonds,
		s1,
		sm,
		so,
		mapFile,
		mol,
		inFile,
		tSInd,
		tDInd,
		outFile,
		sInds,
		dInds,
		numSort,
		tinds,
		ms;

	mapFile = config.mapfile;
	inFile = config.infile;
	outFile = config.outfile;

	function myTrim(e) {
		return e.trim().length > 0;
	}

	fm = new File(mapFile);
	fm.open(File.ReadOnly);

	mla = [];
	sm = new TextStream(fm);
	mapLines = [];
	if (config.header) {
		line = sm.readLine();
	}
	do {
		line = sm.readLine();
		if (line.length > 0) {
			mapLines.push(line);
		}
	} while (!sm.atEnd());
	fm.close();

	//set regex to split map lines
	if (config.separator == RenumberMolecule.COMMA) {
		regexp = /\s*,\s*/;
	} else if (config.separator == RenumberMolecule.SEMICOLON) {
		regexp = /\s*;\s*/;
	} else {
		regexp = /(\s+)/;
	}

	la = [];
	laOut = [];
	lines = [];
	if (config.activeMol) {
		mol = molecule.activeMolecule();
		try {
			la = mol.getMolfile().split(/\r?\n/);
		} catch (e) {
			throw new Error('no molecule selected');
		}
		if (la[la.length - 1].length == 0) {
			la = la.splice(0, la.length - 1);
		}
	} else {
		f = new File(inFile);
		f.open(File.ReadOnly);
		s1 = new TextStream(f);
		do {
			line = s1.readLine();
			la.push(line);
		} while (!s1.atEnd());
		f.close();
	}

	lt = la[3].split(/(\s+)/).filter(myTrim);
	natoms = parseInt(lt[0], 10);
	nbonds = parseInt(lt[1], 10);

	//validate map file

	if (mapLines[0].split(regexp).filter(myTrim).length != 2) {
		throw new Error("the map file is not in the expected format - check file and settings");
	}
	//check map is closed set
	sInds = [];
	dInds = [];
	numSort = function (a, b) {
		return a - b;
	};
	for (j = 0; j < mapLines.length; j++) {
		inds = mapLines[j].split(regexp).filter(myTrim);
		tSInd = parseInt(inds[0], 10) - 1;
		tDInd = parseInt(inds[1], 10) - 1;
		sInds.push(tSInd);
		dInds.push(tDInd);
	}
	sInds.sort(numSort);
	dInds.sort(numSort);
	//check not more lines than atoms
	if (sInds[sInds.length - 1] >= natoms) {
		throw new Error("There are entries in source column of map file (column 1) that are greater than the number of atoms in the molecule");
	}
	if (dInds[sInds.length - 1] >= natoms) {
		throw new Error("There are entries in destination column of map file (column 2) that are greater than the number of atoms in the molecule");
	}
	for (j = 0; j < sInds.length; j++) {
		if (sInds[j] !== dInds[j]) {
			throw new Error("the map file does not have a closed list of mappings. For example if you map atom 2 to atom 3, you must also map atom 3 to atom 2");
		}
	}

	//check no map number (source or dest) appears twice
	for (j = 1; j < sInds.length; j++) {
		if (sInds[j] === sInds[j - 1]) {
			throw new Error("the map file has two entries for the same atom in source column: " + (sInds[j] + 1));
		}
		if (dInds[j] === dInds[j - 1]) {
			throw new Error("the map file has two entries for the same atom in destination column: " + (dInds[j] + 1));
		}
	}



	for (i = 0; i < 4; i++) {
		laOut[i] = la[i];
	}

	for (i = 0; i < natoms; i++) {
		ind = i;
		for (j = 0; j < mapLines.length; j++) {
			inds = mapLines[j].split(regexp).filter(myTrim);
			tSInd = parseInt(inds[0], 10) - 1;
			tDInd = parseInt(inds[1], 10) - 1;
			if (tSInd === i) {
				ind = tDInd;
			}
		}
		laOut[ind + 4] = la[i + 4];
	}

	for (i = 0; i < nbonds; i++) {
		inds = la[i + 4 + natoms].split(/(\s+)/).filter(myTrim);
		a1 = inds[0] - 1;
		a2 = inds[1] - 1;
		na1 = a1;
		na2 = a2;
		for (j = 0; j < mapLines.length; j++) {
			tinds = mapLines[j].split(regexp).filter(myTrim);
			tSInd = parseInt(tinds[0], 10) - 1;
			tDInd = parseInt(tinds[1], 10) - 1;
			if (tSInd === a1) {
				na1 = tDInd;
			}
			if (tSInd === a2) {
				na2 = tDInd;
			}
		}
		laOut[i + 4 + natoms] = RenumberMolecule.padString((na1 + 1).toString(), 3) + RenumberMolecule.padString((na2 + 1).toString(), 3);
		for (j = 2; j < inds.length; j++) {
			laOut[i + 4 + natoms] = laOut[i + 4 + natoms] + RenumberMolecule.padString(inds[j], 3);
		}
	}

	laOut[la.length - 1] = la[la.length - 1];
	if (config.addMol) {
		ms = "";
		for (i = 0; i < laOut.length; i++) {
			ms = ms + laOut[i] + "\n";
		}
		molecule.importMolfile(ms);
	} else {
		fo = new File(outFile);
		fo.open(File.WriteOnly);

		so = new TextStream(fo);
		for (i = 0; i < laOut.length; i++) {
			so.writeln(laOut[i]);
		}
		fo.close();
	}
};

RenumberMolecule.prototype.run = function () {
	"use strict";

	var config, error;

	config = RenumberMolecule.makeRenumberDialog();

	if (config.ok === 1) {
		try {
			if (config.mapfile.localeCompare(config.infile) === 0) {
				error = {};
				error.message = "Input and output file cannot have the same name";
				throw error;
			}
			RenumberMolecule.renumberMol(config);
			MessageBox.information("Finished renumbering .mol file");
		} catch (e) {
			MessageBox.information("Error renumbering .mol file: \n" + e.message);
		}
	} else {
		//MessageBox.information("Cancelled renumbering .mol file");
	}
};

if (this.MnUi && this.MnUi.scripts_common && typeof molecule !== "undefined") {
	MnUi.scripts_common.scripts_common_RenumberMolecule = function () {
		"use strict";

		var renumberMolecule;

		try {
			renumberMolecule = new RenumberMolecule();
			renumberMolecule.run();
		} catch (e) {
			MessageBox.critical(e);
		}
	};
}
